﻿using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Linq.Expressions;
using Tizer.Core;
using Tizer.Core.Helpers;
using Tizer.Core.Enums;

namespace Tizer.Core.Repository
{
    public abstract class BaseRepository<TEntity> : IDisposable where TEntity : EntityObject
    {
        private ObjectContext _context;
        private IObjectSet<TEntity> _objectSet;
        private bool _dbCreated;
        
        public BaseRepository()
        {
            _objectSet = this.Context.CreateObjectSet<TEntity>();
        }

        public BaseRepository(ObjectContext context)
        {
            _context = context;
            _objectSet = this.Context.CreateObjectSet<TEntity>();
        }

        public BaseRepository(string connectionString)
    	{
            _context = new TizerEntities(connectionString);
            _objectSet = this.Context.CreateObjectSet<TEntity>();
    	}

        public ObjectContext Context
        {
            get 
            { 
                if (_context == null)
                {
                    _dbCreated = true;
                    _context = new TizerEntities();
                }
                return _context;
            }
        }

        public void Dispose()
        {
            if (_context != null && _dbCreated)
            {
                _context.Dispose();
                _context = null;
            }
        }

        protected IObjectSet<TEntity> ObjectSet
        {
            get { return _objectSet ?? (_objectSet = this.Context.CreateObjectSet<TEntity>()); }
        }

        /// <summary>
        /// Fluent-запрос для выборки
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> GetQuery()
        {
            return ObjectSet;
        }

        /// <summary>
        /// Получить все сущности
        /// </summary>
        /// <returns>Перечень сущностей</returns>
        public IEnumerable<TEntity> GetAll()
        {   
            return GetQuery().ToList();
        }


      
        /// <summary>
        /// Получить страницу
        /// </summary>
        /// <param name="start">Номер страницы</param>
        /// <param name="limit">Размер страницы</param>
        /// <param name="sort">Сортировка по полю</param>
        /// <param name="dir">Направление сортировки ("ASC" или "DESC")</param>
        /// <param name="count">Общее количество объектов</param>
        /// <returns>Список</returns>
        public List<TEntity> GetPage(int start, int limit, string sort, SortDir dir, out int count)
        {
            var result = GetQuery();
            
            GetPageQueryable(start, limit, sort, dir, ref result);

            count = GetQuery().Count();
            
            return result.ToList();
        }


        protected void GetPageQueryable(int start, int limit, string sort, SortDir dir, ref IQueryable<TEntity> result)
        {
            if (!string.IsNullOrEmpty(sort))
            {
                result = dir == SortDir.ASC
                             ? SortByQuery<TEntity>(result, sort, true)
                             : SortByQuery<TEntity>(result, sort, false);
            }

            if (start >= 0 && limit > 0)
            {
                result = result.Skip(start).Take(limit);
            }
        }

        /// <summary>
        /// Сортировка
        /// </summary>
        /// <typeparam name="T">Объект</typeparam>
        /// <param name="q">Запрос</param>
        /// <param name="sort">Сортировать по полю</param>
        /// <param name="ascending">Направление сортировки</param>
        /// <returns>Перечень сущностей</returns>
        public IQueryable<T> SortByQuery<T>(IQueryable<T> q, string sort, bool ascending)
        {
            var p = Expression.Parameter(typeof(T), "e");

            //foreach(PropertyInfo pi in typeof(T).GetProperties())
            //{
            //    if (pi.PropertyType == typeof(string))
            //    {
            //        var x = Expression.Lambda<Func<T, string>>(Expression.Property(p, pi.Name), p);
            //        //q = q.Where(x.)
            //    }
            //}

            if (typeof(T).GetProperty(sort).PropertyType == typeof(int?))
            {
                var x = Expression.Lambda<Func<T, int?>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else if (typeof(T).GetProperty(sort).PropertyType == typeof(int))
            {
                var x = Expression.Lambda<Func<T, int>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else if (typeof(T).GetProperty(sort).PropertyType == typeof(DateTime))
            {
                var x = Expression.Lambda<Func<T, DateTime>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else if (typeof(T).GetProperty(sort).PropertyType == typeof(DateTime?))
            {
                var x = Expression.Lambda<Func<T, DateTime>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else if (typeof(T).GetProperty(sort).PropertyType == typeof(Int64))
            {
                var x = Expression.Lambda<Func<T, Int64>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else if (typeof(T).GetProperty(sort).PropertyType == typeof(bool))
            {
                var x = Expression.Lambda<Func<T, bool>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            else
            {
                var x = Expression.Lambda<Func<T, object>>(Expression.Property(p, sort), p);
                q = ascending ? q.OrderBy(x) : q.OrderByDescending(x);
            }
            return q;
        }

        /// <summary>
        /// Получить единственную сущность по параметрам
        /// </summary>
        /// <param name="where">Параметр</param>
        /// <returns>Сущность</returns>
        public TEntity Single(Func<TEntity, bool> where)
        {
            return this.ObjectSet.SingleOrDefault<TEntity>(where);
        }

        /// <summary>
        /// Первую сущность
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public TEntity First(Func<TEntity, bool> where)
        {
            return this.ObjectSet.FirstOrDefault<TEntity>(where);
        }

        /// <summary>
        /// Удалить сущность
        /// </summary>
        /// <param name="entity">Сущность</param>
        public virtual void Delete(TEntity entity)
        {
            this.ObjectSet.DeleteObject(entity);
        }

       /// <summary>
        /// Добавить сущность
        /// </summary>
        /// <param name="entity">Сущность</param>
        public virtual void Add(TEntity entity)
        {
            this.ObjectSet.AddObject(entity);
        }

        /// <summary>
        /// Добавить сущность к контексту
        /// </summary>
        /// <param name="entity">Сущность</param>
        public void Attach(TEntity entity)
        {
            this.ObjectSet.Attach(entity);
        }


        /// <summary>
        /// Сохранить изменения
        /// </summary>
        public void SaveChanges()
        {
            this.Context.SaveChanges();
        }

    }
}
